package com.minelx.table.core.series.source;

import com.minelx.table.core.series.Series;

import java.util.Arrays;
import java.util.function.Function;
import java.util.stream.LongStream;
import java.util.stream.Stream;

public interface Source<E> {
	E at(long at);

	long len();

	Stream<E> stream();

	default <R> Source<R> map(Function<? super E, R> mapper) {
		return new MappedSource<>(this, mapper);
	}

	default Source<E> set(long at, E value) {
		return new Source<E>() {
			@Override
			public E at(long innerAt) {
				if (innerAt == at) {
					return value;
				}
				return Source.this.at(innerAt);
			}

			@Override
			public Stream<E> stream() {
				return LongStream.range(0, len())
						.mapToObj(this::at);
			}

			@Override
			public long len() {
				return Source.this.len();
			}
		};
	}

	static Source<Long> range(long startInclusive, long endExclusive) {
		return new Source<Long>() {
			@Override
			public Long at(long at) {
				if (at < 0 || at >= len()) {
					throw new ArrayIndexOutOfBoundsException("index can bigger than len, index: " + at);
				}
				return at + startInclusive;
			}

			@Override
			public long len() {
				return endExclusive - startInclusive;
			}

			@Override
			public Stream<Long> stream() {
				return LongStream.range(startInclusive, endExclusive).boxed();
			}
		};
	}

	static <E> Source<E> array(E[] source) {
		return new Source<E>() {
			@Override
			public E at(long at) {
				return source[(int) at];
			}

			@Override
			public Stream<E> stream() {
				return Arrays.stream(source);
			}

			@Override
			public long len() {
				return source.length;
			}
		};
	}

	static <E> Source<E> concat(Source<? extends E> left, Source<? extends E> right) {
		return new Source<E>() {
			@Override
			public E at(long at) {
				long leftLen = left.len();
				if (at < leftLen) {
					return left.at(at);
				} else {
					return right.at(at - leftLen);
				}
			}

			@Override
			public Stream<E> stream() {
				return Stream.concat(left.stream(), right.stream());
			}

			@Override
			public long len() {
				return left.len() + right.len();
			}
		};
	}

	static <E> Source<E> concat2(Series<? extends E> left, Series<? extends E> right) {
		return new Source<E>() {
			@Override
			public E at(long at) {
				long leftLen = left.len();
				if (at < leftLen) {
					return left.at(at);
				} else {
					return right.at(at - leftLen);
				}
			}

			@Override
			public Stream<E> stream() {
				return Stream.concat(left.stream(), right.stream());
			}

			@Override
			public long len() {
				return left.len() + right.len();
			}
		};
	}

	static <E> Source<E> fill(E by, long len) {
		return new Source<E>() {
			@Override
			public E at(long at) {
				if (at < 0 || at >= len) {
					throw new ArrayIndexOutOfBoundsException("index can bigger than len, index: " + at);
				}
				return by;
			}

			@Override
			public long len() {
				return len;
			}

			@Override
			public Stream<E> stream() {
				return Stream.generate(() -> by)
						.limit(len);
			}
		};
	}
}
